2024_cage_music_of_changes.py

#

SPDX-FileCopyrightText: 2025 Ana Beatriz Bonfim Ferreira & Larissa Silva Feital SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

#

Blender 4.2.3 LTS / AliceLab / AIM1 / 2024.2 John Cage - Music of Changes - Formal interpretation by procedural modeling Work: I(Change) By BONFIM Ana and FEITAL Larissa

import bpy
import random
import math
#

1 Step: Cleaning the Scene

bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
bpy.ops.outliner.orphans_purge()
#

2 Step: Defining the vertices and faces for the seven elements (notes)

elements = {
    "cubeDóObj": {
        "verts": [
            (-1.13, -1.27, -1.06),
            (-1.0, -1.0, -0.44),
            (-1.0, 1.0, -1.0),
            (-1.13, 1.13, -0.19),
            (1.0, -1.0, -1.0),
            (0.99, -1.23, -0.22),
            (0.92, 1.18, -1),
            (1, 1, -0.44),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
    "cubeRéObj": {
        "verts": [
            (-1.0, -1.0, -1.0),
            (-1.11, -1.24, 0.19),
            (-1.0, 1.0, -1.0),
            (-1.0, 1.0, 0.02),
            (1.0, -1.0, -1.0),
            (1.0, -1.0, 0.02),
            (1.0, 1.0, -1.0),
            (1.0, 1.0, 0.02),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
    "cubeMiObj": {
        "verts": [
            (-1.0, -1.0, -1.0),
            (-1.65, -1.45, 1.69),
            (-1.0, 1.0, -1.0),
            (-1.12, 0.63, 1.01),
            (1.0, -1.0, -1.0),
            (0.94, -0.63, 1.19),
            (1.0, 1.0, -1.0),
            (1.69, 0.95, 0.005),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
    "cubeFaObj": {
        "verts": [
            (-1.0, -1.0, -1.0),
            (-0.78, -0.95, 0.24),
            (-1.0, 1.0, -0.41),
            (-0.78, 1.04, 0.24),
            (0.87, -0.95, -0.57),
            (1.21, -0.95, 0.24),
            (1.0, 1.0, -1.0),
            (0.89, -0.30, 1.41),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
    "cubeSolObj": {
        "verts": [
            (-1.54, -0.21, -1.92),
            (-0.93, -0.33, 1.21),
            (-0.64, 0.49, -0.40),
            (-0.95, 0.57, 1.03),
            (1.26, -0.37, -1.34),
            (1.0, -1.0, 1.0),
            (0.61, 0.16, 1.84),
            (-0.61, -0.23, 3.27),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
    "cubeLaObj": {
        "verts": [
            (-1.0, -1.0, -1.0),
            (-1.48, -1.04, 1.46),
            (-1.0, 1.0, -1.0),
            (-1.16, 0.09, 1.24),
            (1.0, -1.0, -1.0),
            (0.91, -0.48, 1.02),
            (1.71, -0.23, -1.56),
            (1.0, 1.0, 1.0),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
    "cubeSiObj": {
        "verts": [
            (-1.0, -1.0, -1.0),
            (-1.65, -1.45, 1.69),
            (-1.0, 1.0, -1.0),
            (-1.12, 0.63, 1.01),
            (1.0, -1.0, -1.0),
            (0.94, -0.63, 1.19),
            (1.0, 1.0, -1.0),
            (1.69, 0.95, 0.005),
        ],
        "faces": [
            (0, 1, 3, 2),
            (2, 3, 7, 6),
            (6, 7, 5, 4),
            (4, 5, 1, 0),
            (2, 6, 4, 0),
            (7, 3, 1, 5),
        ],
    },
}
#

3 Step: Randomly decide the number of elements

num_elements = random.randint(8, 64)
print(f"Generating {num_elements} elements...")
#

4 Step: Create a 3D grid layout (cube shape) to place elements

grid_size = math.ceil(num_elements ** (1 / 3))
positions = [
    (x, y, z)
    for x in range(grid_size)
    for y in range(grid_size)
    for z in range(grid_size)
]

random.shuffle(positions)
selected_positions = positions[:num_elements]
#

5 Step: Define a mapping for dynamics to scale factors

dynamics_to_scale = {
    "ppp": 0.1,
    "pp": 0.2,
    "p": 0.3,
    "mp": 0.4,
    "mf": 0.5,
    "f": 0.6,
    "ff": 0.7,
    "fff": 0.8,
}
#

6 Step: Loop to place and modify objects

for pos in selected_positions:
    chosen_element_name = random.choice(list(elements.keys()))
    verts = elements[chosen_element_name]["verts"]
    faces = elements[chosen_element_name]["faces"]
#

Create the mesh and object

    mesh = bpy.data.meshes.new(f"{chosen_element_name}_Mesh")
    obj = bpy.data.objects.new(chosen_element_name, mesh)
    bpy.context.collection.objects.link(obj)
    bpy.context.view_layer.objects.active = obj

    mesh.from_pydata(verts, [], faces)
    mesh.update()
#

Assign a random dynamic level and apply scaling

    dynamic_level = random.choice(list(dynamics_to_scale.keys()))
    print(dynamic_level)
    scale_factor = dynamics_to_scale[dynamic_level]
    print(scale_factor)
    obj.scale = (scale_factor,) * 3
#

Table of Dynamics (For visible elements, to rotate and scale based on even and odd numbers)

    obj.location = pos
#

Table of Duration (Defining the pauses)

    bpy.ops.mesh.primitive_uv_sphere_add(
        radius=random.choice([0.3, 0.5, 0.6]), location=pos
    )
    sphere = bpy.context.active_object
    sphere.name = f"BooleanSphere_{pos}"
    sphere.hide_viewport = True
    sphere.hide_render = True
#

Carving it out (Creating the sphere)

    bool_modifier = obj.modifiers.new(name="Boolean", type="BOOLEAN")
    bool_modifier.object = sphere
    bool_modifier.operation = "DIFFERENCE"
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.modifier_apply(modifier="Boolean")
#

Carving it out 2 (Making sure the sphere is removed so you can see the effect of the pauses)

    bpy.data.objects.remove(sphere, do_unlink=True)
#

Fim :)